// FluidSynthWrapper.h

#pragma once

using namespace System;

#include "..\fluidsynth-1.0.8\include\fluidsynth.h"

namespace FluidSynthWrapper {

	ref class Synthesizer;
	ref class Settings;
	ref class AudioDriver;
	ref class InteropHelper;
	ref class Player;
	ref class Event;
	ref class Sequencer;

	public ref class Settings
	{
	private:
		::fluid_settings_t* settings;

		void SetInt(String^ settingName, int settingValue);
		void SetStr(String^ settingName, String^ settingValue);
		void SetNum(String^ settingName, double settingValue);
		void SetBool(String^ settingName, bool settingValue);

		int GetInt(String^ settingName);
		String^ GetStr(String^ settingName);
		double GetNum(String^ settingName);
		bool GetBool(String^ settingName);

	public:
		Settings();
		!Settings();
		~Settings(){this->!Settings();}

		// Start of synth settings
		property double SynthGain{
			double get(){return GetNum("synth.gain");}
			void set(double value){SetNum("synth.gain",value);}
		}
		property double SynthSampleRate{
			double get(){return GetNum("synth.sample-rate");}
			void set(double value){SetNum("synth.sample-rate",value);}
		}
		property int SynthPolyphony{
			int get(){return GetInt("synth.polyphony");}
			void set(int value){SetInt("synth.polyphony",value);}
		}
		property int SynthMidiChannels{
			int get(){return GetInt("synth.midi-channels");}
			void set(int value){SetInt("synth.midi-channels",value);}
		}
		property bool SynthReverbActive{
			bool get(){return GetBool("synth.reverb.active");}
			void set(bool value){SetBool("synth.reverb.active",value);}
		}
		property bool SynthChorusActive{
			bool get(){return GetBool("synth.chorus.active");}
			void set(bool value){SetBool("synth.chorus.active",value);}
		}
		property bool SynthLadspaActive{
			bool get(){return GetBool("synth.ladspa.active");}
			void set(bool value){SetBool("synth.ladspa.active",value);}
		}
		property int SynthAudioGroups{
			int get(){return GetInt("synth.audio-groups");}
			void set(int value){SetInt("synth.audio-groups",value);}
		}
		property int SynthAudioChannels{
			int get(){return GetInt("synth.audio-channels");}
			void set(int value){SetInt("synth.audio-channels",value);}
		}
		property int SynthEffectsChannels{
			int get(){return GetInt("synth.effects-channels");}
			void set(int value){SetInt("synth.effects-channels",value);}
		}
		property bool SynthVerbose{
			bool get(){return GetBool("synth.verbose");}
			void set(bool value){SetBool("synth.verbose",value);}
		}
		property bool SynthDump{
			bool get(){return GetBool("synth.dump");}
			void set(bool value){SetBool("synth.dump",value);}
		}
		// End of synth settings

		// Start of audio settings
		property String^ AudioDriver{
			String^ get(){return GetStr("audio.driver");}
			void set(String^ value){SetStr("audio.driver",value);}
		}
		property int AudioPeriods{
			int get(){return GetInt("audio.periods");}
			void set(int value){SetInt("audio.periods",value);}
		}
		property int AudioPeriodSize{
			int get(){return GetInt("audio.period-size");}
			void set(int value){SetInt("audio.period-size",value);}
		}
		property String^ AudioSampleFormat{
			String^ get(){return GetStr("audio.sample-format");}
			void set(String^ value){SetStr("audio.sample-format",value);}
		}
		// End of audio settings

		//int SettingGetInteger(String^ settingName);
		//String^ SettingGetString(String^ settingName);

		::fluid_settings_t* GetSettingsObject(void);
	};

	public ref class AudioDriver
	{
	private:
		::fluid_audio_driver_t* driver;
	public:
		AudioDriver(Settings^ settings, Synthesizer^ synth);
		!AudioDriver();
		~AudioDriver(){this->!AudioDriver();}
	};

	public ref class Synthesizer
	{
	private:
		::fluid_synth_t* synth;
		Settings^ settingsObj;
	public:
		Synthesizer(Settings^ settings);
		!Synthesizer();
		~Synthesizer(){this->!Synthesizer();}
		void NoteOn(int channel, short pitch, short velocity);
		void NoteOff(int channel, short pitch);
		void ProgChange(int channel, int prog);
		void ProgChange(int channel, int prog, int bank, int id);
		void ControllerChange(int channel, int controller, int value);
		void PitchBend(int channel, short value);
		void BankSelect(int channel, unsigned int bank);
		int SFontLoad(String^ path);
		void SFontUnload(unsigned int id, bool reset);
		void SelectSoundFont(int channel, int id);
		::fluid_synth_t* GetSynthObject();
		double GetCPULoad();
		void Reset();
		array<short>^ FillBuffer(Int32 length);
		static bool IsSoundFont(String^ filename);
		property int SoundFontCount{
			int get(){return ::fluid_synth_sfcount(this->synth);}
		}
	};

	public ref class Player
	{
	private:
		::fluid_player_t* player;
	public:
		Player(Synthesizer^ synth,String^ filename);
		!Player();
		~Player(){this->!Player();}

		void Play();
		void Stop();
		property int Tempo{
			void set(int value){::fluid_player_set_midi_tempo(this->player,value);}
		}
	};

	public ref class Event
	{
	private:
		::fluid_event_t* ev;
		Event(::fluid_event_t* e);
	public:
		!Event();
		~Event(){this->!Event();}
		static Event^ Note(int channel, short pitch, short velocity, int duration);
		static Event^ NoteOn(int channel, short pitch, short velocity);
		static Event^ NoteOff(int channel, short pitch);
		static Event^ ControlChange(int channel, short controller, short value);
		static Event^ ProgChange(int channel, short patch);
		static Event^ PitchBend(int channel, int value);
		static Event^ Pan(int channel, short value);
		static Event^ Volume(int channel, short value);
		::fluid_event_t* GetEventObject();
	};

	public ref class Sequencer
	{
	private:
		::fluid_sequencer_t* seq;
		short seqSynthId;
	public:
		Sequencer();
		!Sequencer();
		~Sequencer(){this->!Sequencer();}
		void AddSynthesizer(Synthesizer^ synth);
		void PlayEvent(Event^ ev);
		void PlayEventAt(Event^ ev, unsigned int time, bool absolute);
		unsigned int GetCurrentTick();
		double GetTickScale();
		void SetTickScale(double tps);
		void SendNoteOn(int chan, short key, unsigned int time);
	};

	private ref class InteropHelper
	{
	public:
		static char* StringToChar(String^ oldString);
	};
}